#ifndef XMLLITE_XMLELEMENT_H_
#define XMLLITE_XMLELEMENT_H_

#include <iosfwd>
#include <string>

#include "base/scoped_ptr.h"
#include "xmllite/qname.h"

namespace buzz {

	class XmlChild;
	class XmlText;
	class XmlElement;
	class XmlAttr;

	class XmlChild {
	public:
		XmlChild* NextChild() { return next_child_; }
		const XmlChild* NextChild() const { return next_child_; }

		bool IsText() const { return IsTextImpl(); }

		XmlElement* AsElement() { return AsElementImpl(); }
		const XmlElement* AsElement() const { return AsElementImpl(); }

		XmlText* AsText() { return AsTextImpl(); }
		const XmlText* AsText() const { return AsTextImpl(); }


	protected:
		XmlChild() :
			 next_child_(NULL) {
			 }

			 virtual bool IsTextImpl() const = 0;
			 virtual XmlElement* AsElementImpl() const = 0;
			 virtual XmlText* AsTextImpl() const = 0;


			 virtual ~XmlChild();

	private:
		friend class XmlElement;

		XmlChild(const XmlChild& noimpl);

		XmlChild* next_child_;
	};

	class XmlText : public XmlChild {
	public:
		explicit XmlText(const std::string& text) :
		XmlChild(),
			text_(text) {
		}
		explicit XmlText(const XmlText& t) :
		XmlChild(),
			text_(t.text_) {
		}
		explicit XmlText(const char* cstr, size_t len) :
		XmlChild(),
			text_(cstr, len) {
		}
		virtual ~XmlText();

		const std::string& Text() const { return text_; }
		void SetText(const std::string& text);
		void AddParsedText(const char* buf, int len);
		void AddText(const std::string& text);

	protected:
		virtual bool IsTextImpl() const;
		virtual XmlElement* AsElementImpl() const;
		virtual XmlText* AsTextImpl() const;

	private:
		std::string text_;
	};

	class XmlAttr {
	public:
		XmlAttr* NextAttr() const { return next_attr_; }
		const QName& Name() const { return name_; }
		const std::string& Value() const { return value_; }

	private:
		friend class XmlElement;

		explicit XmlAttr(const QName& name, const std::string& value) :
		next_attr_(NULL),
			name_(name),
			value_(value) {
		}
		explicit XmlAttr(const XmlAttr& att) :
		next_attr_(NULL),
			name_(att.name_),
			value_(att.value_) {
		}

		XmlAttr* next_attr_;
		QName name_;
		std::string value_;
	};

	class XmlElement : public XmlChild
	{
	public:
		explicit XmlElement(const QName& name);
		explicit XmlElement(const QName& name, bool useDefaultNs);
		explicit XmlElement(const XmlElement& elt);

		virtual ~XmlElement();

		const QName& Name() const { return name_; }
		void SetName(const QName& name) { name_ = name; }

		const std::string BodyText() const;
		void SetBodyText(const std::string& text);

		const QName FirstElementName() const;

		XmlAttr* FirstAttr();
		const XmlAttr* FirstAttr() const
		{ return const_cast<XmlElement *>(this)->FirstAttr(); }

		// Attr will return an empty string if the attribute isn't there:
		// use HasAttr to test presence of an attribute.
		const std::string Attr(const StaticQName& name) const;
		const std::string Attr(const QName& name) const;
		bool HasAttr(const StaticQName& name) const;
		bool HasAttr(const QName& name) const;
		void SetAttr(const QName& name, const std::string& value);
		void ClearAttr(const QName& name);

		XmlChild* FirstChild();
		const XmlChild* FirstChild() const {
			return const_cast<XmlElement *>(this)->FirstChild();
		}

		XmlElement* FirstElement();
		const XmlElement* FirstElement() const {
			return const_cast<XmlElement *>(this)->FirstElement();
		}

		XmlElement* NextElement();
		const XmlElement* NextElement() const {
			return const_cast<XmlElement *>(this)->NextElement();
		}

		XmlElement* FirstWithNamespace(const std::string& ns);
		const XmlElement* FirstWithNamespace(const std::string& ns) const {
			return const_cast<XmlElement *>(this)->FirstWithNamespace(ns);
		}

		XmlElement* NextWithNamespace(const std::string& ns);
		const XmlElement* NextWithNamespace(const std::string& ns) const {
			return const_cast<XmlElement *>(this)->NextWithNamespace(ns);
		}

		XmlElement* FirstNamed(const StaticQName& name);
		const XmlElement* FirstNamed(const StaticQName& name) const {
			return const_cast<XmlElement *>(this)->FirstNamed(name);
		}

		XmlElement* FirstNamed(const QName& name);
		const XmlElement* FirstNamed(const QName& name) const {
			return const_cast<XmlElement *>(this)->FirstNamed(name);
		}

		XmlElement* NextNamed(const StaticQName& name);
		const XmlElement* NextNamed(const StaticQName& name) const {
			return const_cast<XmlElement *>(this)->NextNamed(name);
		}

		XmlElement* NextNamed(const QName& name);
		const XmlElement* NextNamed(const QName& name) const {
			return const_cast<XmlElement *>(this)->NextNamed(name);
		}

		// Finds the first element named 'name'.  If that element can't be found then
		// adds one and returns it.
		XmlElement* FindOrAddNamedChild(const QName& name);

		const std::string TextNamed(const QName& name) const;

		void InsertChildAfter(XmlChild* predecessor, XmlChild* new_child);
		void RemoveChildAfter(XmlChild* predecessor);

		void AddParsedText(const char* buf, int len);
		// Note: CDATA is not supported by XMPP, therefore using this function will
		// generate non-XMPP compatible XML.
		void AddCDATAText(const char* buf, int len);
		void AddText(const std::string& text);
		void AddText(const std::string& text, int depth);
		void AddElement(XmlElement* child);
		void AddElement(XmlElement* child, int depth);
		void AddAttr(const QName& name, const std::string& value);
		void AddAttr(const QName& name, const std::string& value, int depth);
		void ClearNamedChildren(const QName& name);
		void ClearAttributes();
		void ClearChildren();

		static XmlElement* ForStr(const std::string& str);
		std::string Str() const;

		bool IsCDATA() const { return cdata_; }

	protected:
		virtual bool IsTextImpl() const;
		virtual XmlElement* AsElementImpl() const;
		virtual XmlText* AsTextImpl() const;

	private:
		QName name_;
		XmlAttr* first_attr_;
		XmlAttr* last_attr_;
		XmlChild* first_child_;
		XmlChild* last_child_;
		bool cdata_;
	};

}  // namespace buzz

#endif  // XMLLITE_XMLELEMENT_H_
